
/**
 *
 */
package com.serviceprovider.business.module.admin.transaction.controller;

//~--- non-JDK imports --------------------------------------------------------

import com.serviceprovider.business.module.admin.transaction.model.FileUpload;
import com.serviceprovider.business.module.admin.transaction.model.NewCsvReplica;
import com.serviceprovider.business.module.admin.transaction.services.dao.interfaces.ITransactionDataService;
import com.serviceprovider.business.module.admin.transaction.services.dao.interfaces.ITransactionImportDataService;
import com.serviceprovider.business.module.admin.transaction.utility.StringUtils;
import com.serviceprovider.business.module.admin.transaction.valueobjects.CsvMapping;
import com.serviceprovider.business.module.admin.transaction.valueobjects.TransactionCSV;
import com.serviceprovider.business.module.admin.transaction.valueobjects.TransactionCSVError;
import com.serviceprovider.business.module.admin.transaction.valueobjects.TransactionCSVHistory;
import com.serviceprovider.business.utility.RequestConstants;

import org.apache.log4j.Logger;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.multipart.commons.CommonsMultipartFile;
import org.springframework.web.servlet.ModelAndView;

import org.supercsv.cellprocessor.ift.CellProcessor;
import org.supercsv.io.CsvBeanReader;
import org.supercsv.io.ICsvBeanReader;
import org.supercsv.prefs.CsvPreference;

//~--- JDK imports ------------------------------------------------------------

import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;

import java.net.URLEncoder;

import java.text.SimpleDateFormat;

import java.util.ArrayList;
import java.util.Date;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;

/**
 * @author mrduongnv@gmail.com
 *
 */
@Controller
@SuppressWarnings("unchecked")
public class FileUploadController {
    private static final String ERROR_LIST   = "errorList";
    private static final String MIX_LIST     = "mixList";
    private static final String SUCCESS_LIST = "successList";

    /**
     * The logger
     */
    private final Logger                  logger = Logger.getLogger(getClass());
    @Autowired
    private CsvMapping                    csvMapping;
    @Autowired
    private ServletContext                servletContext;
    @Autowired
    private ITransactionDataService       transactionDataService;
    @Autowired
    private ITransactionImportDataService transactionImportDataService;

    @ModelAttribute
    @RequestMapping(
        value  = RequestConstants.TransactionRequests.IMPORT_DATA,
        method = RequestMethod.GET
    )
    public ModelAndView showForm() {
        ModelAndView mov = new ModelAndView(RequestConstants.TransactionRequests.VIEW_IMPORT_DATA);

        mov.addObject("fileUpload", new FileUpload());

        return mov;
    }

    @RequestMapping(
        value  = RequestConstants.TransactionRequests.IMPORT_DATA,
        method = RequestMethod.POST
    )
    public ModelAndView onSubmit(FileUpload fileUpload, BindingResult errors, HttpServletRequest request)
            throws Exception {
        ModelAndView mov = new ModelAndView(RequestConstants.TransactionRequests.VIEW_IMPORT_DATA);

        try {
            if (request.getParameter("cancel") != null) {
                return new ModelAndView(RequestConstants.TransactionRequests.VIEW_TRANSACTION_SUMMARY);
            }

            final CommonsMultipartFile file = fileUpload.getFile();

            if ((file == null) || file.isEmpty()) {
                errors.rejectValue("file", "file.empty", "Please select a file to upload.");
            } else {
                byte[]       buffer   = file.getBytes();
                final String fileName = file.getOriginalFilename();

                if (!fileName.contains(".csv")) {
                    errors.rejectValue("file", "file.select.csv.file", "Please select a valid csv file.");
                } else {
                    String                      fileDestination       = uploadFile(request, file, buffer);
                    final TransactionCSVHistory transactionCsvHistory =
                        this.transactionDataService.retrieveTransactionCsvHistory(URLEncoder.encode(fileDestination,
                            "UTF-8").toString());
                    Integer                   historyId   = transactionCsvHistory.getId();
                    final Map<String, List>   map         = loadCSVData(fileDestination, historyId);
                    List<TransactionCSVError> errorList   = new ArrayList<TransactionCSVError>();
                    List<TransactionCSVError> mixList     = new ArrayList<TransactionCSVError>();
                    List<TransactionCSV>      successList = new ArrayList<TransactionCSV>();

                    if (map.get(SUCCESS_LIST) != null) {
                        successList = map.get(SUCCESS_LIST);
                    }

                    if (map.get(ERROR_LIST) != null) {
                        errorList = map.get(ERROR_LIST);
                    }

                    if (map.get(MIX_LIST) != null) {
                        mixList = map.get(MIX_LIST);
                    }

                    if (!successList.isEmpty() &&!mixList.isEmpty() &&!errorList.isEmpty()) {
                        mov.addObject(
                            "message",
                            "Some of the Transaction data imported sucessfully and some of the imported records have errors. Duplicate records also found and not imported.");
                        mov.addObject(SUCCESS_LIST, successList);
                        mov.addObject(MIX_LIST, mixList);
                        mov.addObject(ERROR_LIST, errorList);
                    } else if (successList.isEmpty() &&!mixList.isEmpty() &&!errorList.isEmpty()) {
                        mov.addObject(
                            "message",
                            "Transaction data imported sucessfully with having errors. Duplicate records also found and not imported.");
                        mov.addObject(MIX_LIST, mixList);
                        mov.addObject(ERROR_LIST, errorList);
                    } else if (!successList.isEmpty() && mixList.isEmpty() &&!errorList.isEmpty()) {
                        mov.addObject(
                            "message",
                            "Transaction data imported sucessfully without having errors. Duplicate records also found and not imported.");
                        mov.addObject(SUCCESS_LIST, successList);
                        mov.addObject(ERROR_LIST, errorList);
                    } else if (!successList.isEmpty() &&!mixList.isEmpty() && errorList.isEmpty()) {
                        mov.addObject(
                            "message",
                            "Transaction data imported sucessfully with having errors. No duplicate records found.");
                        mov.addObject(SUCCESS_LIST, successList);
                        mov.addObject(MIX_LIST, mixList);
                    } else if (!successList.isEmpty()) {
                        this.transactionDataService.markAsProcessedTransactionHistory(historyId);
                        mov.addObject("message",
                                      "Some of the Transaction data imported sucessfully. No duplicate records found.");
                        mov.addObject(SUCCESS_LIST, successList);
                    } else if (!mixList.isEmpty()) {
                        mov.addObject(
                            "message",
                            "Transaction data imported sucessfully with having errors. No duplicate records found.");
                        mov.addObject(MIX_LIST, mixList);
                    } else if (!errorList.isEmpty()) {
                        mov.addObject("message", "All the records are duplicated.");
                        mov.addObject(ERROR_LIST, errorList);
                    }
                }
            }
        } catch (Exception e) {
            this.logger.error("Error occured while importing data.", e);
            errors.rejectValue("file", "file.error.occured", "Error occured while importing data.");
        }

        return mov;
    }

    /**
     * Upload file.
     *
     * @param request
     *            the request
     * @param file
     *            the file
     * @param buffer
     *            the buffer
     * @return the file destination with name
     * @throws IOException
     *             Signals that an I/O exception has occurred.
     * @throws FileNotFoundException
     *             the file not found exception
     */
    private String uploadFile(HttpServletRequest request, final CommonsMultipartFile file, byte[] buffer)
            throws IOException, FileNotFoundException {
        SimpleDateFormat format     = new SimpleDateFormat("yyyy/MM/dd/hh/mm/ss");
        final String     remoteUser = request.getRemoteUser();
        String           uploadDir  = this.servletContext.getRealPath("/resources") + "/" + remoteUser + "/"
                           + format.format(new Date()) + "/";
        File dirPath = new File(uploadDir);

        if (!dirPath.exists()) {
            dirPath.mkdirs();
        }

        InputStream  stream           = file.getInputStream();
        final String originalFilename = file.getOriginalFilename();
        final String destination      = uploadDir + originalFilename;
        OutputStream bos              = new FileOutputStream(destination);
        int          bytesRead;

        while ((bytesRead = stream.read(buffer, 0, buffer.length)) != -1) {
            bos.write(buffer, 0, bytesRead);
        }

        bos.close();
        stream.close();
        saveHistory(remoteUser, uploadDir, originalFilename);

        return destination;
    }

    /**
     * Save history.
     *
     * @param remoteUser
     *            the remote user
     * @param uploadDir
     *            the upload directory
     * @param originalFilename
     *            the original filename
     * @throws UnsupportedEncodingException
     *             the unsupported encoding exception
     */
    private void saveHistory(String remoteUser, String uploadDir, String originalFilename)
            throws UnsupportedEncodingException {
        TransactionCSVHistory history = new TransactionCSVHistory();

        history.setFileName(originalFilename);
        history.setPath(URLEncoder.encode(uploadDir + originalFilename, "UTF-8").toString());
        history.setCreatedDate(new Date());
        history.setUserName(remoteUser);
        this.transactionDataService.insertTransactionHistory(history);
    }

    /**
     * Load csv data.
     *
     * @param fileDestination
     *            the file destination
     * @param historyId
     *            the history id
     * @return the error list
     * @throws Exception
     *             the exception
     */
    public Map<String, List> loadCSVData(String fileDestination, Integer historyId) throws Exception {
        Map<String, List>         map         = new LinkedHashMap<String, List>();
        List<TransactionCSVError> errorList   = new ArrayList<TransactionCSVError>();
        List<TransactionCSVError> mixList     = new ArrayList<TransactionCSVError>();
        List<NewCsvReplica>       successList = new ArrayList<NewCsvReplica>();
        ICsvBeanReader            inFile      = null;

        inFile = new CsvBeanReader(new FileReader(fileDestination), CsvPreference.EXCEL_PREFERENCE);
        this.csvMapping.getCsvMappingMap().size();

        String[]          header     = inFile.getCSVHeader(true);
        ArrayList<String> headerList = new ArrayList<String>();

        for (int i = 0; i < header.length; i++) {
            String key   = header[i];
            String value = this.csvMapping.getCsvMappingMap().get(key);

            headerList.add(value);
        }

        header = headerList.toArray(header);

        NewCsvReplica newCsvReplica;

        while ((newCsvReplica = inFile.read(NewCsvReplica.class, header, getUserProcessors())) != null) {
            if (validateNewCsvReplica(newCsvReplica.getTrace_number())) {
                successList.add(newCsvReplica);
            }

            newCsvReplica.setHistoryId(historyId.intValue());
            this.logger.info(StringUtils.constructToString(newCsvReplica));
            this.transactionImportDataService.insertTransaction(newCsvReplica);
        }

        inFile.close();
        map.put(SUCCESS_LIST, successList);
        map.put(ERROR_LIST, errorList);
        map.put(MIX_LIST, mixList);

        return map;
    }

    /**
     * Validate new csv replica.
     *
     * @param traceNumber
     *            the trace number
     * @return true, if successful
     */
    private boolean validateNewCsvReplica(String traceNumber) {
        if (StringUtils.isEmpty(traceNumber)) {
            return false;
        }

        return !this.transactionImportDataService.checkIfTransactionPresent(traceNumber);
    }

    /**
     * Gets the user processors.
     *
     * @return the user processors
     */
    private CellProcessor[] getUserProcessors() {
        return new CellProcessor[] {
            null, null, null, null, null, null, null, null, null, null, null, null
        };
    }

    /**
     * Validate transaction csv.
     *
     * @param transactionCSV
     *            the transaction csv
     * @return the error object
     */
}



